package main

func main() {
	// P的数量
	// runtime.GOMAXPROCS(runtime.NumCPU())

	// GMP模型
	/*

		GMP是golang的线程模型，包含三个概念：内核线程（M）、goroutine（G）、G的上下文环境（P）
		G：goroutine协程，基于协程建立的用户态线程
		M：machine，它直接关联一个os内核线程，用于执行G
		P：processor处理器，P里面一般会存当前goruntine运行的上下文环境（函数指针、堆栈地址及地址边界），P会对自己管理的goruntine队列做一些调度
	*/
	/*
		在Go中，线程是运行goroutine的实体，调度器的功能是把可运行的goruntine分配到工作线程上
		1. 全局队列：存放等待运行的G
		2. P的本地队列：同全局队列类似，存放的也是等待运行的G，存放的数量有限不超过256个。
			新建G时，G优先加入到P的本地队列，如果队列满了，则会把本地队列中一半的G移动到全局队列
		3. P列表：所有的P都在程序启动时创建，并保存在数组中，最多有GOMAXPROCS（可配置）个
		4. M：线程想运行任务就得获取P，从P的本地队列获取G，P队列为空时，M也会尝试从全局队列拿一批G放到P的本地队列，或从其他P的本地队列一半放到自己的P本地队列。
			M运行G，G执行之后，M会从P获取下一个G，不断重复下去

		P和M的数量问题
		P的数量：环境变量 $GOMAXPROCS；在程序中通过runtime.GOMAXPROCS() 来设置
		M的数量：GO语言本省限定一万（但是操作系统达不到）；通过runtime/debug包中的SetMaxThreads函数来设置；有一个M阻塞，会创建一个新的M，如果有M空闲，那么就会回收或者休眠
		M与P的数量没有绝对关系，一个M阻塞，P就会去创建或者切换另一个M，所以即使P的默认数量是1，也有可能会创建很多个M出来

		调度器的设计策略
		1. 复用线程
		2. 利用并行
		3. 抢占
		4. 全局G队列

		复用线程：
			避免频繁的创建、销毁线程，而是对线程的复用
				work stealing：当从绑定的P获取不到G，尝试从全局队列中获取，获取不到从其他P中偷取一半
				hand off：当M调用G阻塞，则M与P解绑，把P转移给其他空闲的M执行
		利用并行：
			最多有GOMAXPROCS个线程分布在多个CPU上同时运行，GOMAXPROCS也限制了并发的程度
		抢占：
			基于协作的抢占式调度，以前只能依靠goroutine主动让出CPU资源才能触发调度，之后实现了基于信号的抢占式调度，一个goroutine最多占用CPU 10ms，防止其他goroutine饿死
		全局G队列：
			在新的调度器中依然有全局G队列，但功能被弱化了，当M执行workstealing从其他P偷不到G时，它可以从全局G队列获取G
	*/

	/*
		P拥有G1，M获取P后开始运行G1，G1使用go func() 创建了G2，为了局部性G2优先加入到P1的本地队列
		G1执行完成后（函数：goexit），M上运行的goroutine切换为G0，G0负责调度时协程的切换（函数：schedule）。
			从P的本地队列取G2，从G0切换到G2，并开始运行G2（函数：execute）。实现了线程M1的复用
		G溢出，假如本地队列已满，需要执行负载均衡（把本地队列中前一半的G，还有新创建G转移到全局队列），本地队列未满则加入本地队列
			移走一半是为了防止后面G饥饿
		唤醒正在休眠的M
			规定：在创建G时，运行的G会尝试唤醒其他空闲的P和M组合去执行
			假定G2唤醒了M2，M2绑定了P2，并运行G0，但P2本地队列没有G，M2此时为自旋线程（没有G但为运行状态的线程，不断寻找G）
			先从全局队列中获取，没有再从其他线程中偷取（先全后偷）
		自旋线程获取G
			M2尝试从全局队列（简称GQ） 取一批G放到P2的本地队列（函数：findrunnable()）。M2从全局队列的G数量符合下面的公式
				n = min(len(GQ) / GOMAXPROCS + 1, cap(LQ) / 2)
				GQ：全局队列总长度（队列中现在元素的个数）
				GOMAXPROCS：P的个数
		偷取
		全局队列已经没有G，那M就要执行work stealing（偷取）：从其他有G的P哪里偷取一半G过来，放到自己的P本地队列。
			P2从P1的本地队列尾部取一半的G，
			偷取队列元素的一半
		自旋线程的最大限制
			正在运行的M + 自旋线程 <= GOMAXPROCS
			如果M大于P，则进入休眠线程队列

		G发送系统调用/阻塞，若G运行阻塞后M和P会立即解绑，若P队列里有G、全局队列有G或有空闲的M，P2会立马唤醒一个M和它绑定，否则P则会加入空闲P列表等待M来获取可用的P
			自旋线程抢占G，不抢占P
	*/
}
